/*
 * Copyright (c) 2024.  little3201.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.server.starter.exploiter.controller;

import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.server.starter.exploiter.dto.FieldDTO;
import com.server.starter.exploiter.dto.SchemaDTO;
import com.server.starter.exploiter.service.FieldService;
import com.server.starter.exploiter.service.SchemaService;
import com.server.starter.exploiter.vo.FieldVO;
import com.server.starter.exploiter.vo.SchemaVO;
import com.server.starter.exploiter.vo.TemplateVO;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.OutputStream;
import java.util.List;

@RestController
@RequestMapping("/schemas")
public class SchemaController {

    private final Logger logger = LoggerFactory.getLogger(SchemaController.class);

    private final SchemaService schemaService;
    private final FieldService fieldService;

    /**
     * Constructor for Controller.
     *
     * @param schemaService a {@link SchemaService} object
     * @param fieldService  a {@link FieldService} object
     */
    public SchemaController(SchemaService schemaService, FieldService fieldService) {
        this.schemaService = schemaService;
        this.fieldService = fieldService;
    }

    /**
     * Retrieves a paginated list of records.
     *
     * @param page       The page number.
     * @param size       The number of records per page.
     * @param sortBy     The field to sort by.
     * @param descending Whether sorting should be in descending order.
     * @param name       The name filter for the schemas.
     * @return A paginated list of schemas, or 204 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:read')")
    @GetMapping
    public ResponseEntity<Page<SchemaVO>> retrieve(@RequestParam int page, @RequestParam int size,
                                                   String sortBy, boolean descending, String name) {
        Page<SchemaVO> voPage;
        try {
            voPage = schemaService.retrieve(page, size, sortBy, descending, name);
        } catch (Exception e) {
            logger.error("Retrieve schema occurred an error: ", e);
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(voPage);
    }

    /**
     * Fetches a record by ID.
     *
     * @param id The record ID.
     * @return The record data, or 204 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:read')")
    @GetMapping("/{id}")
    public ResponseEntity<SchemaVO> fetch(@PathVariable Long id) {
        SchemaVO vo;
        try {
            vo = schemaService.fetch(id);
        } catch (Exception e) {
            logger.error("Fetch schema occurred an error: ", e);
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(vo);
    }

    /**
     * Checks if a record exists by name.
     *
     * @param name The record name.
     * @param id   The record ID.
     * @return True if the record exists, or 204 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:read')")
    @GetMapping("/exists")
    public ResponseEntity<Boolean> exists(@RequestParam String name, Long id) {
        boolean exists;
        try {
            exists = schemaService.exists(name, id);
        } catch (Exception e) {
            logger.info("Query schema exists occurred an error: ", e);
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(exists);
    }

    /**
     * Creates a new record.
     *
     * @param dto The record data transfer object.
     * @return The created record, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:write')")
    @PostMapping
    public ResponseEntity<SchemaVO> create(@RequestBody @Valid SchemaDTO dto) {
        SchemaVO vo;
        try {
            boolean existed = schemaService.exists(dto.getName(), null);
            if (existed) {
                return ResponseEntity.status(HttpStatus.CONFLICT).build();
            }
            vo = schemaService.create(dto);
        } catch (Exception e) {
            logger.error("Create schema occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
        }
        return ResponseEntity.status(HttpStatus.CREATED).body(vo);
    }

    /**
     * Modifies an existing record.
     *
     * @param id  The record ID.
     * @param dto The record data transfer object.
     * @return The modified record, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:write')")
    @PutMapping("/{id}")
    public ResponseEntity<SchemaVO> modify(@PathVariable Long id, @RequestBody @Valid SchemaDTO dto) {
        SchemaVO vo;
        try {
            boolean existed = schemaService.exists(dto.getName(), id);
            if (existed) {
                return ResponseEntity.status(HttpStatus.CONFLICT).build();
            }
            vo = schemaService.modify(id, dto);
        } catch (Exception e) {
            logger.error("Modify schema occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
        }
        return ResponseEntity.accepted().body(vo);
    }

    /**
     * Removes a record by ID.
     *
     * @param id The record ID.
     * @return 200 status code if successful, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:write')")
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> remove(@PathVariable Long id) {
        try {
            schemaService.remove(id);
        } catch (Exception e) {
            logger.error("Remove schema occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
        }
        return ResponseEntity.ok().build();
    }

    /**
     * Fetches fields by record ID.
     *
     * @param id The record ID.
     * @return The list of fields, or 204 status code if an error occurs.
     */
    @GetMapping("/{id}/fields")
    public ResponseEntity<List<FieldVO>> fields(@PathVariable Long id) {
        List<FieldVO> voList;
        try {
            voList = fieldService.findAllBySchemaId(id);
        } catch (Exception e) {
            logger.error("Fetch schema field occurred an error: ", e);
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(voList);
    }

    /**
     * Modifies fields for a record.
     *
     * @param id      The record ID.
     * @param dtoList The list of field data transfer objects.
     * @return The list of modified fields, or 204 status code if an error occurs.
     */
    @PatchMapping("/{id}/fields")
    public ResponseEntity<List<FieldVO>> modifyFields(@PathVariable Long id, @RequestBody List<FieldDTO> dtoList) {
        List<FieldVO> voList;
        try {
            // enable
            schemaService.enable(id);
            voList = fieldService.modifyAll(id, dtoList);
        } catch (Exception e) {
            logger.error("Fetch schema field occurred an error: ", e);
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(voList);
    }

    /**
     * Synchronizes the record.
     *
     * @param id The record ID.
     * @return 200 status code if successful, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:write')")
    @PatchMapping("/{id}/sync")
    public ResponseEntity<Void> sync(@PathVariable Long id) {
        try {
            schemaService.sync(id);
        } catch (Exception e) {
            logger.error("Sync occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
        }
        return ResponseEntity.ok().build();
    }

    /**
     * Downloads the record as a ZIP file.
     *
     * @param id       The record ID.
     * @param response The HTTP response.
     * @return 200 status code if successful, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:write')")
    @PostMapping("/{id}/download")
    public ResponseEntity<Void> download(@PathVariable Long id, HttpServletResponse response) {
        // 设置响应头
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=\"templates.zip\"");
        try (OutputStream os = response.getOutputStream()) {
            schemaService.generate(id, os);
        } catch (Exception e) {
            logger.error("Generate occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
        }
        return ResponseEntity.ok().build();
    }

    /**
     * Preview a record.
     *
     * @param id The record ID.
     * @return The list of records value objects, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:read')")
    @GetMapping("/{id}/preview")
    public ResponseEntity<List<TemplateVO>> preview(@PathVariable Long id) {
        List<TemplateVO> voList;
        try {
            voList = schemaService.preview(id);
        } catch (Exception e) {
            logger.error("Generate codes occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
        }
        return ResponseEntity.ok().body(voList);
    }

    /**
     * Import the records.
     *
     * @return 200 status code if successful, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:import')")
    @PostMapping("/import")
    public ResponseEntity<List<SchemaVO>> importFromExcel(MultipartFile file) {
        List<SchemaVO> voList;
        try {
            ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
            List<SchemaDTO> dtoList = reader.readAll(SchemaDTO.class);
            voList = schemaService.createAll(dtoList);
        } catch (Exception e) {
            logger.error("Import schemas occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
        }
        return ResponseEntity.ok().body(voList);
    }

    /**
     * Export the records.
     *
     * @return The list of records value objects, or 417 status code if an error occurs.
     */
    @PreAuthorize("hasAuthority('SCOPE_schemas:export')")
    @GetMapping("/export")
    public ResponseEntity<Void> exportToExcel(@RequestParam(required = false) List<Long> ids, HttpServletResponse response) {
        try (ExcelWriter writer = ExcelUtil.getWriter(true);
             ServletOutputStream out = response.getOutputStream()) {
            List<SchemaVO> voList = schemaService.retrieve(ids);

            writer.write(voList, true);
            //response为HttpServletResponse对象
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=data.xlsx");

            writer.flush(out, true);
        } catch (Exception e) {
            logger.error("Export schemas occurred an error: ", e);
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
        }
        return ResponseEntity.ok().build();
    }

}
